#ifndef __SOCKET_TCP_SSL_H__
#define __SOCKET_TCP_SSL_H__ 

#include "pandabase.h"
#include "config_nativenet.h"
#include "socket_ip.h"
#include "numeric_types.h"

#ifdef HAVE_OPENSSL

#include <openssl/rsa.h>       /* SSLeay stuff */
#include <openssl/crypto.h>
#include <openssl/x509.h>
#include <openssl/pem.h>
#include <openssl/ssl.h>
#include <openssl/err.h>

/////////////////////////////////////////////////////////////////////
// Class : Socket_TCP_SSL
//
// Description : 
//
/////////////////////////////////////////////////////////////////////

extern EXPCL_PANDA_NATIVENET SSL_CTX *global_ssl_ctx;


struct SSlStartup
{
   SSlStartup()
   {
        const SSL_METHOD *meth;
        SSLeay_add_ssl_algorithms();
        //meth = SSLv23_server_method();
        meth = SSLv23_method();
        SSL_load_error_strings();
        // I hate this cast, but older versions of OpenSSL need it.
        global_ssl_ctx = SSL_CTX_new ((SSL_METHOD *) meth);                        
   }

   ~SSlStartup()
   {
        SSL_CTX_free (global_ssl_ctx);    
        global_ssl_ctx = NULL;
   }
   

   bool isactive() { return global_ssl_ctx != NULL; };
};


class EXPCL_PANDA_NATIVENET Socket_TCP_SSL : public Socket_IP
{
public:
    
    inline Socket_TCP_SSL(SOCKET);
    inline Socket_TCP_SSL() : _ssl(NULL) {}

    virtual inline ~Socket_TCP_SSL()
    {
        CleanSslUp();
    }
    
    inline int SetNoDelay();
    inline int SetLinger(int interval_seconds = 0);
    inline int DontLinger();
    
    inline int SetSendBufferSize(int insize);
    inline bool ActiveOpen(const Socket_Address & theaddress);
    inline int SendData(const char * data, int size);
    inline int RecvData(char * data, int size);
    inline bool ErrorIs_WouldBlocking(int err);

    inline SSL * get_ssl() { return _ssl; };

    inline void DetailErrorFormat(void);
private:
      SSL*     _ssl;

        void CleanSslUp()
        {
            if(_ssl != NULL)
            {
                SSL_shutdown(_ssl);
                SSL_free(_ssl);
                _ssl = NULL;
            }
        }
  
public:
  static TypeHandle get_class_type() {
    return _type_handle;
  }
  static void init_type() {
    Socket_IP::init_type();
    register_type(_type_handle, "Socket_TCP_SSL",
                  Socket_IP::get_class_type());
  }
  virtual TypeHandle get_type() const {
    return get_class_type();
  }
  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}

private:
  static TypeHandle _type_handle;
};

//////////////////////////////////////////////////////////////
// Function name : Socket_TCP_SSL::Socket_TCP_SSL
// Description   :
//////////////////////////////////////////////////////////////
// right know this will only work for a 
// accepted ie a server socket ??
inline Socket_TCP_SSL::Socket_TCP_SSL(SOCKET sck) : ::Socket_IP(sck)
{ 
    SetNonBlocking(); // maybe should be blocking?
  
    _ssl = SSL_new (global_ssl_ctx);                         
    if(_ssl == NULL) 
        return;
    SSL_set_fd (_ssl,(int)GetSocket() );

    SSL_accept(_ssl);
    ERR_clear_error();

//    printf(" Ssl Accept = %d \n",err);
}

////////////////////////////////////////////////////////////////////
// Function name : SetNoDelay
// Description   : Disable Nagle algorithm. Don't delay send to coalesce packets
////////////////////////////////////////////////////////////////////
inline int Socket_TCP_SSL::SetNoDelay()
{
    int nodel = 1;
    int ret1;
    ret1 = setsockopt(_socket, IPPROTO_TCP, TCP_NODELAY, (char *) & nodel, sizeof(nodel));
    
    if (ret1 != 0)
        return BASIC_ERROR;
    
    return ALL_OK;
}

////////////////////////////////////////////////////////////////////
// Function name : SetLinger
// Description   : will control the behavior of SO_LINGER for a TCP socket
////////////////////////////////////////////////////////////////////
int Socket_TCP_SSL::SetLinger(int interval_seconds)
{
    linger ll;
    ll.l_linger = interval_seconds;
    ll.l_onoff = 1;
    int ret1 = setsockopt(_socket, SOL_SOCKET, SO_LINGER, (const char *) & ll, sizeof(linger));
    if (ret1 != 0)
        return BASIC_ERROR;
    return ALL_OK;
}

////////////////////////////////////////////////////////////////////
// Function name : Socket_TCP_SSL::DontLinger
// Description   : Turn off the linger flag. The socket will quickly release
//        buffered items and free up OS resources. You may lose
//        a stream if you use this flag and do not negotiate the close
//        at the application layer.
////////////////////////////////////////////////////////////////////
int Socket_TCP_SSL::DontLinger()
{
    linger ll;
    ll.l_linger = 0;
    ll.l_onoff = 0;
    int ret1 = setsockopt(_socket, SOL_SOCKET, SO_LINGER, (const char *) & ll, sizeof(linger));
    if (ret1 != 0)
        return BASIC_ERROR;
    return ALL_OK;
}

////////////////////////////////////////////////////////////////////
// Function name : SetSendBufferSize
// Description   : Just like it sounds. Sets a buffered socket recv buffer size.
//      This function does not refuse ranges outside hard-coded OS
//      limits
////////////////////////////////////////////////////////////////////
int Socket_TCP_SSL::SetSendBufferSize(int insize)
{
    if (setsockopt(_socket, (int) SOL_SOCKET, (int) SO_SNDBUF, (char *) &insize, sizeof(int)))
        return BASIC_ERROR;
    return ALL_OK;
}

////////////////////////////////////////////////////////////////////
// Function name : ActiveOpen
// Description   : This function will try and set the socket up for active open to a specified
//       address and port provided by the input parameter
////////////////////////////////////////////////////////////////////
bool Socket_TCP_SSL::ActiveOpen(const Socket_Address & theaddress)
{
    _socket = DO_NEWTCP();
    if (_socket == BAD_SOCKET)
        return false;
    
    if (DO_CONNECT(_socket, &theaddress.GetAddressInfo()) != 0)
        return ErrorClose();
    

    _ssl = SSL_new (global_ssl_ctx);                         
    if(_ssl == NULL) 
        return false;
    SSL_set_fd (_ssl,(int)GetSocket() );
    if(SSL_connect(_ssl) == -1)
        return false;
    return true;

    //return SetSslUp();
}

////////////////////////////////////////////////////////////////////
// Function name : Socket_TCP_SSL::SendData
// Description   : Ok Lets Send the Data
//
// Return type  : int
//      - if error
//      0 if socket closed for write or lengh is 0
//      + bytes writen ( May be smaller than requested)
////////////////////////////////////////////////////////////////////
inline int Socket_TCP_SSL::SendData(const char * data, int size)
{
    if(_ssl == NULL)
        return -1;

//    ERR_clear_error();

    return SSL_write(_ssl, data, size);
}

////////////////////////////////////////////////////////////////////
// Function name : Socket_TCP_SSL::RecvData
// Description   : Read the data from the connection
//
// Return type  : int
//      - if error
//      0 if socket closed for read or length is 0
//      + bytes read ( May be smaller than requested)
////////////////////////////////////////////////////////////////////
inline int Socket_TCP_SSL::RecvData(char * data, int len)
{
    if(_ssl == NULL)
        return -1;

    ERR_clear_error();

    return SSL_read(_ssl, data, len);
}

////////////////////////////////////////////////////////////////////
// Function name : ErrorIs_WouldBlocking
// Description   : Is last error a blocking error ??
//
// Return type  : Bool
//      True is last error was a blocking error
////////////////////////////////////////////////////////////////////
inline bool Socket_TCP_SSL::ErrorIs_WouldBlocking(int err)
{
    if(_ssl == NULL || err >= 0)
    {
      nativenet_cat.warning()
        << "Socket_TCP_SSL::ErrorIs_WouldBlocking->Called With Error number "
        << err << " or _ssl is NULL\n";
        return false;
    }

    int ssl_error_code = SSL_get_error(_ssl,err);
    bool answer = false;
    
    switch(ssl_error_code)
    {
        case SSL_ERROR_WANT_READ:
        case SSL_ERROR_WANT_WRITE:
        case SSL_ERROR_WANT_CONNECT:
//        case SSL_ERROR_WANT_ACCEPT:
            answer = true;
            break;
// hmm not sure we need this .. hmmmm
        case SSL_ERROR_SYSCALL:
            if(GETERROR() == LOCAL_BLOCKING_ERROR)
                answer = true;
            else
            {
                DetailErrorFormat();
//                LOGWARNING("Socket_TCP_SSL::ErrorIs_WouldBlocking-> Not A blocking Error1 SSl_CODe=[%d] OS=[%d]",ssl_error_code,GETERROR());
            }
            break;
        default:
            DetailErrorFormat();
//          LOGWARNING("Socket_TCP_SSL::ErrorIs_WouldBlocking-> Not A blocking Error2 SSl_CODe=[%d] OS=[%d]",ssl_error_code,GETERROR());
            answer = false;
            break;
    }

//    ERR_clear_error();
    return answer;
}

inline void Socket_TCP_SSL::DetailErrorFormat(void)
{
    return; // turn on fir debuging

    PN_uint32 l;
    char buf[256];
    char buf2[4096];
    const char *file,*data;
    int line,flags;
    PN_uint32 es;

    es=CRYPTO_thread_id();
    while ((l=ERR_get_error_line_data(&file,&line,&data,&flags)) != 0)
    {
        ERR_error_string_n(l, buf, sizeof( buf) );
        BIO_snprintf(buf2, sizeof(buf2), "***%lu:%s:%s:%d:%s\n", (unsigned long) es, buf,file, line, (flags & ERR_TXT_STRING) ? data : "NoText");
        nativenet_cat.warning()
          << "Socket_TCP_SSL::DetailErrorFormat->[" << buf2 << "]\n";
    }
}

#endif  // HAVE_OPENSSL

#endif //__SOCKET_TCP_SSL_H__

